using System;
using System.Collections.Generic;
using Contrib.AppFabric.Configuration;
using Contrib.AppFabric.Models;
using Contrib.Cache.Models;
using Microsoft.ApplicationServer.Caching;
using Orchard;
using Orchard.ContentManagement;
using Orchard.Logging;

namespace Contrib.AppFabric.Services {
    public class AppFabricServices : IAppFabricServices {
        private readonly IOrchardServices _orchardServices;
        private readonly IAppFabricClient _client;

        private DataCache _entries;

        private const string InternalAppFabricRetryCacheKey = "InternalAppFabricRetryCacheKey";
        private const string InternalAppFabricCacheHoldCacheKey = "InternalAppFabricCacheHoldCacheKey";

        // Track whether Dispose has been called.
        private bool _disposed = false;

        public AppFabricServices(IOrchardServices orchardServices, 
            IAppFabricClient client) {
            _orchardServices = orchardServices;
            _client = client;

            Logger = NullLogger.Instance;
        }

        public ILogger Logger { get; set; }

        public CacheItem Get(string cacheKey) {
            try {
                if (_orchardServices.WorkContext.HttpContext.Cache[InternalAppFabricCacheHoldCacheKey] == null) {
                    return Entries.Get(cacheKey) as CacheItem;
                }
            }
            catch (Exception exception) {
                Logger.Error(exception, "An error occured when attempting to retrieve the cache item {0}", cacheKey);
            }
            return null;
        }

        public void Remove(string cacheKey) {
            try {
                if (_orchardServices.WorkContext.HttpContext.Cache[InternalAppFabricCacheHoldCacheKey] == null) {
                    Entries.Remove(cacheKey);
                }
            }
            catch (Exception exception) {
                Logger.Error(exception, "An error occured when attempting to remove the cache item {0}", cacheKey);
            }
        }

        public void Add(string cacheKey, CacheItem cacheItem, TimeSpan timeSpan) {
            try {
                if (_orchardServices.WorkContext.HttpContext.Cache[InternalAppFabricCacheHoldCacheKey] == null) {
                    Entries.Add(cacheKey, cacheItem, timeSpan);
                }
            }
            catch (Exception exception) {
                Logger.Error(exception, "An error occured when attempting to add the cache item {0} to the internal appfabric cache", cacheKey);
            }
        }

        public IEnumerable<CacheItem> GetCacheItems() {
            foreach (var region in GetSystemRegions()) {
                foreach (var cacheEntry in GetObjectsInRegion(region)) {
                    var cacheItem = cacheEntry.Value as CacheItem;
                    if (cacheItem != null) {
                        yield return cacheItem;
                    }
                }
            }
        }

        public IEnumerable<string> GetSystemRegions() {
            return Entries.GetSystemRegions();
        }

        public IEnumerable<KeyValuePair<string, object>> GetObjectsInRegion(string region) {
            return Entries.GetObjectsInRegion(region);
        }

        public IDictionary<string, List<CacheItem>> GetRegionLevelCaches() {
            var caches = new Dictionary<string, List<CacheItem>>();

            foreach (var region in GetSystemRegions()) {
                var cacheItems = new List<CacheItem>();

                foreach (var cacheEntry in GetObjectsInRegion(region)) {
                    var cacheItem = cacheEntry.Value as CacheItem;
                    if (cacheItem != null) {
                        cacheItems.Add(cacheItem);
                    }
                }

                caches.Add(region, cacheItems);
            }

            return caches;
        }

        private DataCache Entries {
            get {
                if (_entries == null) {
                    _entries = _client.GetCache(_orchardServices.WorkContext.CurrentSite.Get<AppFabricSettingsPart>().CacheName);

                    _entries.CacheOperationCompleted += CacheOperationCompleted;
                    _entries.CacheOperationStarted += CacheOperationStarted;

                    // TODO : Add Extension Points
                    // Each callback could fire an action that is visible from the UI
                    //_entries.AddFailureNotificationCallback((s, c) => { });
                }
                return _entries;
            }
        }

        void CacheOperationStarted(object sender, CacheOperationStartedEventArgs e) {
        }

        void CacheOperationCompleted(object sender, CacheOperationCompletedEventArgs e) {
            if (!e.HasSucceeded) {
                var settings = _orchardServices.WorkContext.CurrentSite.As<AppFabricSettingsPart>();

                var cacheRetries = _orchardServices.WorkContext.HttpContext.Cache[InternalAppFabricRetryCacheKey];

                if (cacheRetries == null)
                    _orchardServices.WorkContext.HttpContext.Cache[InternalAppFabricRetryCacheKey] = 1;
                else {
                    var retries = ((int) cacheRetries) + 1;

                    var validUntilUtc = DateTime.Now.AddSeconds(settings.SecondsToWaitInRetryCircle);

                    if (settings.MaxiumumNumberOfErrorsUntilRetryCircle == retries) {
                        _orchardServices.WorkContext.HttpContext.Cache.Add(
                            InternalAppFabricCacheHoldCacheKey,
                            true,
                            null,
                            validUntilUtc,
                            System.Web.Caching.Cache.NoSlidingExpiration,
                            System.Web.Caching.CacheItemPriority.Normal,
                            null);

                        Logger.Warning("Appfabric Caching suspended. Exception: {0}", e.ExceptionObject.ToString());
                    }
                    else {
                        _orchardServices.WorkContext.HttpContext.Cache[InternalAppFabricRetryCacheKey] = retries;
                    }
                }
            }
        }

        public void Dispose() {
            Dispose(true);
            GC.SuppressFinalize(this);
        }

        protected virtual void Dispose(bool disposing) {
            if (!this._disposed) {
                if (disposing) {
                    if (_entries != null) {
                        _entries.CacheOperationCompleted -= CacheOperationCompleted;
                        _entries.CacheOperationStarted -= CacheOperationStarted;
                    }
                }
                _disposed = true;
            }
        }

        ~AppFabricServices()
        {
            Dispose(false);
        }
    }
}